﻿#region Imported Libraries
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#endregion
namespace org.webguvenligi.NSecureImageLib.GraphicFileFormats.PNG.Compression
{
	/// <summary>
	/// This Parts Are Taken From IBM's SWT Implementation Engine
	/// </summary>
public class PngLzBlockReader {
	bool isLastBlock;
	byte compressionType;
	int uncompressedBytesRemaining;
	PngDecodingDataStream stream;
	PngHuffmanTables huffmanTables;
	
	byte[] window;
	int windowIndex;
	int copyIndex;
	int copyBytesRemaining;
	
	const int UNCOMPRESSED = 0;
	const int COMPRESSED_FIXED = 1;
	const int COMPRESSED_DYNAMIC = 2;

	const int END_OF_COMPRESSED_BLOCK = 256;
	public const int FIRST_LENGTH_CODE = 257;
	const int LAST_LENGTH_CODE = 285;
	public const int FIRST_DISTANCE_CODE = 1;
	const int LAST_DISTANCE_CODE = 29;
	public  const int FIRST_CODE_LENGTH_CODE = 4;
	public  const int LAST_CODE_LENGTH_CODE = 19;

	static readonly int[] lengthBases = {
		3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 
		31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
	} ;
	static readonly int[] extraLengthBits = {
		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 
		3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0,
	};
	static readonly int[] distanceBases = {
		1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129,
		193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097,
		6145, 8193, 12289, 16385, 24577,
	};
	static readonly int[] extraDistanceBits = {
		0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7,  7,
		8,  8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13,
	};	


public PngLzBlockReader(PngDecodingDataStream stream) {
	this.stream = stream;
	isLastBlock = false;
}

public void setWindowSize(int windowSize) {
	window = new byte[windowSize];
}	

public void readNextBlockHeader() {
	isLastBlock = stream.getNextIdatBit() != 0;
	compressionType = (byte)(stream.getNextIdatBits(2) & 0xFF);
	if (compressionType > 2) stream.error();	
	
	if (compressionType == UNCOMPRESSED) {
		byte b1 = stream.getNextIdatByte();
		byte b2 = stream.getNextIdatByte();
		byte b3 = stream.getNextIdatByte();
		byte b4 = stream.getNextIdatByte();
		if (b1 != ~b3 || b2 != ~b4) stream.error();
		uncompressedBytesRemaining = (b1 & 0xFF) | ((b2 & 0xFF) << 8);
	} else if (compressionType == COMPRESSED_DYNAMIC) {
		huffmanTables = PngHuffmanTables.getDynamicTables(stream);
	} else {
		huffmanTables = PngHuffmanTables.getFixedTables();
	}
}

public byte getNextByte() {
	if (compressionType == UNCOMPRESSED) {
		if (uncompressedBytesRemaining == 0) {
			readNextBlockHeader();
			return getNextByte();
		}
		uncompressedBytesRemaining--;
		return stream.getNextIdatByte();
	} else {
		byte values = GetNextCompressedByte();
		if (values == END_OF_COMPRESSED_BLOCK) {
			if (isLastBlock) stream.error();
			readNextBlockHeader();
			return getNextByte();
		} else {
			return values;
		}
	}
}

private void AssertBlockAtEnd() {
	if (compressionType == UNCOMPRESSED) {
		if (uncompressedBytesRemaining > 0) stream.error();
	} else if (copyBytesRemaining > 0 ||
		(huffmanTables.getNextLiteralValue(stream) != END_OF_COMPRESSED_BLOCK)) 
	{
		stream.error();		
	}
}
public void AssertCompressedDataAtEnd() {
	AssertBlockAtEnd();		
	while (!isLastBlock) {
		readNextBlockHeader();
		AssertBlockAtEnd();
	}	
}

private byte GetNextCompressedByte() {
	if (copyBytesRemaining > 0) {
		byte value = window[copyIndex];
		window[windowIndex] = value;
		copyBytesRemaining--;
		
		copyIndex++;
		windowIndex++;		
		if (copyIndex == window.Length) copyIndex = 0;
		if (windowIndex == window.Length) windowIndex = 0;

		return value;		
	}
	
	int values = huffmanTables.getNextLiteralValue(stream);
	if (values < END_OF_COMPRESSED_BLOCK) {
		window[windowIndex] = (byte) (values & 0xFF);
		windowIndex++;
		if (windowIndex >= window.Length) windowIndex = 0;
		return (byte) (values & 0xFF);		
	} else if (values == END_OF_COMPRESSED_BLOCK) {
		readNextBlockHeader();
		return getNextByte();
	} else if (values <= LAST_LENGTH_CODE) {
		int extraBits = extraLengthBits[values - FIRST_LENGTH_CODE];
		int length = lengthBases[values - FIRST_LENGTH_CODE];
		if (extraBits > 0) {
			length += stream.getNextIdatBits(extraBits);
		}
		
		values = huffmanTables.getNextDistanceValue(stream);
		if (values > LAST_DISTANCE_CODE) stream.error();
		extraBits = extraDistanceBits[values];
		int distance = distanceBases[values];
		if (extraBits > 0) {
			distance += stream.getNextIdatBits(extraBits);
		}
		
		copyIndex = windowIndex - distance;
		if (copyIndex < 0) copyIndex += window.Length;

		copyBytesRemaining = length;
		return GetNextCompressedByte();
	} else {
		stream.error();
		return 0;
	}
}	
}
}
